package biz.datalk.industrialland.admin.config;

import biz.datalk.industrialland.common.shiro.ShiroFilterChainProperties;
import biz.datalk.industrialland.admin.component.shiro.ShiroRealm;
import biz.datalk.industrialland.common.exception.ApplicationException;
import biz.datalk.industrialland.common.shiro.CustomModularRealmAuthenticator;
import biz.datalk.industrialland.common.shiro.CustomSimpleCookie;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * @author tarofang@163.com
 * @date 2019年08月12日
 */
@Configuration
@EnableConfigurationProperties({ShiroFilterChainProperties.class})
public class ShiroConfig {
    private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    @Value("${datalk.core.shiro.unauthorizedurl}")
    private String unauthorizedUrl;

    @Value("${datalk.core.shiro.loginurl}")
    private String loginUrl;

    @Autowired
    private ShiroRealm shiroRealm;

    @Autowired
    private ShiroFilterChainProperties shiroFilterChainProperties;

    @Value("${shiro_use_remember_me: false}")
    private boolean shiroUseRememberMe;

    @Bean
    public CustomModularRealmAuthenticator customModularRealmAuthenticator() {
        return new CustomModularRealmAuthenticator();
    }


    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean(ShiroWebFilterConfiguration.REGISTRATION_BEAN_NAME)
    public FilterRegistrationBean delegatingFilterProxy() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        // proxy.setTargetBeanName("shiroFilter");
        proxy.setTargetBeanName(ShiroWebFilterConfiguration.FILTER_NAME);
        filterRegistrationBean.setFilter(proxy);

        // add 2023-02-21 by tarofang@163.com S
        filterRegistrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
        // add 2023-02-21 by tarofang@163.com E

        return filterRegistrationBean;
    }

    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        // bean.setArguments(securityManager());
        bean.setArguments(securityManager);
        return bean;
    }

    /**
     * Filter Chain定义说明
     * <p>
     * 1、一个URL可以配置多个Filter，使用逗号分隔
     * 2、当设置多个过滤器时，全部验证通过，才视为通过
     * 3、部分过滤器可指定参数，如perms，roles
     */
    @Bean(ShiroWebFilterConfiguration.FILTER_NAME)
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 拦截器
        LinkedHashMap<String, String> filterChainDefinitionMap = getFilterChainDefinitionMap();

        // 定义 IM 的Filter
        LinkedHashMap<String, AccessControlFilter> filters = getFilterChainFilters();
        for (Map.Entry<String, AccessControlFilter> entry : filters.entrySet()) {
            shiroFilterFactoryBean.getFilters().put(entry.getKey(), entry.getValue());
        }


        // 未授权界面返回JSON
        shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //设置自定义的 ModularRealmAuthenticator
        securityManager.setAuthenticator(customModularRealmAuthenticator());

        // 设置 realm
        // securityManager.setRealms(Arrays.asList(shiroRealm));
        securityManager.setRealm(shiroRealm);
        /*
         * 关闭shiro自带的session，详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        // add 2022-07-22 by tarofang@163.com S
        // SECURITY FIX: 屏蔽shiro的响应头， Set-Cookie: rememberMe=deleteMe，因为这是 shiro 攻击的一种特征
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(new CustomSimpleCookie(shiroUseRememberMe));
        securityManager.setRememberMeManager(cookieRememberMeManager);
        // add 2022-07-22 by tarofang@163.com E

        SecurityUtils.setSecurityManager(securityManager);

        return securityManager;
    }


    /** 用来扫描上下文寻找所有的 Advisor（通知器）将符合条件的 Advisor 应用到切入点的 Bean 中 */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /** 管理 shiro bean 的声明周期 */
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /** 使 shiro 的注解生效 */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    private LinkedHashMap<String, AccessControlFilter> getFilterChainFilters() {
        LinkedHashMap<String, AccessControlFilter> filters = new LinkedHashMap<>();
        if (!shiroFilterChainProperties.isEnable()) {
            return filters;
        }

        List<ShiroFilterChainProperties.Chain> chains = shiroFilterChainProperties.getChains();
        if (CollectionUtils.isEmpty(chains)) {
            return filters;
        }
        String filterName, filterClass;
        AccessControlFilter filter;
        Object obj;
        for (ShiroFilterChainProperties.Chain chain : chains) {
            filterName = StringUtils.trimToNull(chain.getFilterName());
            filterClass = StringUtils.trimToNull(chain.getFilterClass());
            if (filterName == null || filterClass == null) {
                continue;
            }
            try {
                obj = ClassUtils.getClass(filterClass).newInstance();
            } catch (Exception ex) {
                logger.error("{}", ex.getMessage());
                throw new ApplicationException(ex);
            }
            if (obj instanceof AccessControlFilter) {
                filter = (AccessControlFilter) obj;
                filters.put(filterName, filter);
                logger.info("Find filter. filterName = {}, filterClass = {}", filterName, filterClass);
            }
        }

        return filters;
    }

    private LinkedHashMap<String, String> getFilterChainDefinitionMap() {
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        if (!shiroFilterChainProperties.isEnable()) {
            return filterChainDefinitionMap;
        }

        List<ShiroFilterChainProperties.Chain> chains = shiroFilterChainProperties.getChains();
        if (CollectionUtils.isEmpty(chains)) {
            return filterChainDefinitionMap;
        }

        String filterName;
        List<String> patterns;
        for (ShiroFilterChainProperties.Chain chain : chains) {
            patterns = chain.getPatterns();
            if (CollectionUtils.isEmpty(patterns)) {
                continue;
            }
            filterName = StringUtils.trimToNull(chain.getFilterName());
            if (filterName == null) {
                continue;
            }
            for (String pattern : patterns) {
                if (StringUtils.isBlank(pattern)) {
                    continue;
                }
                filterChainDefinitionMap.put(pattern.trim(), filterName);
            }
        }

        return filterChainDefinitionMap;
    }

}
